/** (c) 2007 xi8ix, All Rights Reserved. */
package org.xi8ix.loader;

import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.deployer.WebAppDeployer;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.security.HashUserRealm;
import org.mortbay.jetty.security.UserRealm;

import java.io.*;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The main bootstrap server class loads all services in the classpath.
 *
 * @author Iain Shigeoka
 * @invariant $none
 */
public class Loader implements LoaderMBean {
    private static Logger LOG = Logger.getLogger(Loader.class.getName());
    private static final String VERSION = "2.0";
    private static final String TITLE = "Application Loader";
    private final long launch = System.currentTimeMillis();

    private Server webServer;
    private Server pluginServer;

    /**
     * Start the server from the command line. The server ignores all command line arguments.
     *
     * @param args the commandline arguments
     */
    public static void main(String[] args){
        new Thread("loader-boot"){
            public void run() {
                new Loader();
            }
        }.start();
        LOG.log(Level.FINEST,"Starting {0} {1}", new Object[]{TITLE, VERSION});
    }

    /**
     * Loader may only be started using the main method.
     */
    private Loader() {
        addShutdownHook();
        loadConfigProperties();
        File file = new File("server.txt");
        if (!file.exists() || "false".equals(System.getProperty("org.xi8ix.loader.boot.check"))) {
            startWeb();
            if ("swing".equals(System.getProperty("org.xi8ix.loader.boot.mode","none"))) {
                FileReader reader = null;
                try {
                    reader = new FileReader(file);
                    char[] buffer = new char[1024];
                    int read = reader.read(buffer);
                    Util.openURL(new String(buffer,0,read));
                } catch (Exception e) {
                    // Ignore
                    LOG.throwing(Server.class.getName(),"<init>",e);
                } finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        } catch (IOException e) {
                            // Ignore
                            LOG.throwing(Server.class.getName(),"<init>",e);
                        }
                    }
                }
            }
            LOG.log(Level.INFO,"Started application server {0}",VERSION);
        } else {
            LOG.info("Server already appears to be running (server.txt file found). Only one server allowed.");
        }
    }

    public String getTitle() {
        return TITLE;
    }

    public String getVersion() {
        return VERSION;
    }

    public Date getLaunchDate() {
        return new Date(launch);
    }

    public long getUptimeMilliseconds() {
        return System.currentTimeMillis() - launch;
    }

    public String getUptime() {
        return Util.toPrettyDurationString(getUptimeMilliseconds());
    }

    public void shutdown(final long delay) {
        new Thread(){
            /**
             * Shutdown after an optional pause.
             */
            public void run() {
                try {
                    long sleep;
                    if (delay < 250) {
                        sleep = 250;
                    } else {
                        sleep = delay;
                    }
                    Thread.sleep(sleep);
                    System.exit(0);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private void loadConfigProperties() {
        // Now load up user properties set in the properties file
        File propFile = new File("config/telem2m.properties");
        try {
            if (propFile.exists()) {

                System.getProperties().load(new FileInputStream(propFile));
                LOG.finest("Loaded user properties");
            } else {
                InputStream propStream = Server.class.getClassLoader().getResourceAsStream("telem2m.properties");
                if (propStream != null) {
                    System.getProperties().load(propStream);
                    LOG.info("Loaded default properties");
                } else {
                    LOG.info("No config properties loaded");
                }
            }
        } catch (Exception e){
            LOG.throwing(Server.class.getName(),"setup",e);
        }
        System.setProperty("com.telem2m.Server.version",VERSION);
        // Setup xi8ix jdbc library
        System.setProperty("org.xi8ix.jdbc.AutoDataSource.propertyPrefix","com.telem2m.jdbc.");
        System.setProperty("org.xi8ix.jdbc.AutoDataSource.propertyFile","file:config/telem2m.properties");
    }

    private void startWeb() {
        String port = System.getProperty("com.telem2m.web.port","8080");
        // Setup Jetty to use standard Java logging
        org.mortbay.log.Log.setLog(new JettyLogWrapper(null));
        if (LOG.isLoggable(Level.FINEST)) {
            org.mortbay.log.Log.getLog().setDebugEnabled(true);
        } else {
            org.mortbay.log.Log.getLog().setDebugEnabled(false);
        }
        try {
            File pluginsDir = new File("plugins");
            if (pluginsDir.exists() && pluginsDir.isDirectory() && pluginsDir.listFiles().length > 0) {
                PluginDeployer pluginDeployer = new PluginDeployer();
                pluginDeployer.setAllowDuplicates(false);
                pluginDeployer.setExtract(true);
                pluginDeployer.setWebAppDir("plugins");
                pluginServer = new org.mortbay.jetty.Server();
                pluginDeployer.setContexts(pluginServer);
                pluginServer.addLifeCycle(pluginDeployer);
                pluginServer.addConnector(new JettyNoopConnector());
                pluginServer.start();
            } else {
                LOG.info("No plugins found");
            }

            File webappsDir = new File("webapps");
            if (webappsDir.exists() && webappsDir.isDirectory() && webappsDir.listFiles().length > 0) {
                if (port != null && port.trim().length() > 0){
                    String host = null;
                    if (port.contains(":")) {
                        String [] parts = port.split(":");
                        host = parts[0];
                        port = parts[1];
                    }
                    Connector connector = new SelectChannelConnector();
                    if (host != null) {
                        connector.setHost(host);
                    }
                    connector.setPort(Integer.parseInt(port));
                    WebAppDeployer webappDeployer = new WebAppDeployer();
                    webappDeployer.setAllowDuplicates(false);
                    webappDeployer.setExtract(true);
                    webappDeployer.setWebAppDir("webapps");
                    webServer = new org.mortbay.jetty.Server();
                    webappDeployer.setContexts(webServer);
                    webServer.addLifeCycle(webappDeployer);
                    webServer.addConnector(connector);
                    File passwordFile = new File("config/passwords.properties");
                    if (passwordFile.exists()) {
                        HashUserRealm hashUserRealm = new HashUserRealm("WebRealm",passwordFile.getPath());
                        webServer.setUserRealms(new UserRealm[]{hashUserRealm});
                    }
                    webServer.start();

                    if (host == null || host.trim().length() == 0) {
                        host = "127.0.0.1";
                    }
                    StringBuffer url = new StringBuffer();
                    url.append("http://").append(host).append(":").append(connector.getLocalPort()).append("/");
                    File file = new File("server.txt");
                    file.deleteOnExit();
                    FileOutputStream out = new FileOutputStream(file);
                    try {
                        out.write(url.toString().getBytes());
                    } finally {
                        out.close();
                    }
                    LOG.log(Level.FINE,"Started web server on {0}:{1}",new Object[]{host,port});
                }
            } else {
                LOG.info("No webapps found");
            }
        } catch (Exception e) {
            LOG.log(Level.WARNING,"Server startup failure",e);
            e.printStackTrace();
        }
    }

    /**
     * Adds a shutdown hook that properly stops the container's services.
     * By default, the shutdown hook is not added to the JVM runtime.
     */
    private void addShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(){
            public void run() {
                LOG.fine("Stopping container");
                try {
                    if (webServer != null) {
                        webServer.stop();
                    }
                    if (pluginServer != null) {
                        pluginServer.stop();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                LOG.info("Container stopped");
            }
        });
        LOG.finest("Shutdown hook added");
    }
}
